/**
 * 
 */
package it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.netgame;

import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.swing.plaf.basic.BasicInternalFrameTitlePane.MoveAction;

import org.json.simple.JSONObject;

import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.MatchManager;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.AbilityAttackAction;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.ActionCoordinateInfo;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.ActionMediator;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.ActionSpellInfo;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.CharacterAttackAction;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.EndTurnAction;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.GenericActionInfo;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.MovementAction;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.ai.AutomatedPlayerController;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.pawn.Character;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.pawn.stats.StatsFactory;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.player.BattlePlayer;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.world.Map;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.events.AttackEvent;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.events.EndActionEvent;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.events.EventManager;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.events.FinalMovementEventInformation;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.events.GenericEvent;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.events.MovementEvent;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.events.MultipleAttacksEvent;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.events.NoActionEvent;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.events.TurnEvent;

/**
 * @author Massimo Canonaco
 *
 */
public class OnLineEnemyController extends AutomatedPlayerController
{
	/** */
	private BoomerangProtocol connectionProtocol;
	
	/** */
	private boolean sleep;
	
	/** */
	private RunningReceiver runningReceiver;
	
	/** */
	private Lock queueLock;
	
	/** */
	private Condition queueCondition;
	
//	/** */
	private MatchManager mm;
	
	/** */
	private LinkedList<ActionDeposit> actions;
	
	private ServerCondition receiveCondition;
	
	private ServerCondition actionCondition;
	
	private StatsFactory factory;

	private boolean matchIsRunning;
	/** */
	public OnLineEnemyController(BattlePlayer aiPlayer) 
	{
		super(aiPlayer);
		sleep = false;
		receiveCondition = new ServerCondition();
		actionCondition = new ServerCondition();
		runningReceiver = new RunningReceiver(this,receiveCondition,null);
		
		queueLock = new ReentrantLock();
		queueCondition = queueLock.newCondition();
		actions = new LinkedList<>();
	}

	public OnLineEnemyController(StatsFactory factory)
	{
		super(null);
	}
	
	public OnLineEnemyController(StatsFactory factory, BattlePlayer player)
	{
		super(player);
//		super(aiPlayer);
		sleep = false;
		receiveCondition = new ServerCondition();
		actionCondition = new ServerCondition();
		runningReceiver = new RunningReceiver(this,receiveCondition,null);
		
		queueLock = new ReentrantLock();
		queueCondition = queueLock.newCondition();
		actions = new LinkedList<>();
		this.factory = factory;
		matchIsRunning = false;
		
//		this.mm = mm;
	}
	/* (non-Javadoc)
	 * @see it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.ai.AI#makeMove()
	 */
	@Override
	public void makeMove() 
	{
		ActionDeposit action = null;
		ActionMediator mediator = player.getActions();
//		ActionMediator mediator = mm.getEnemy().getActions();
		GenericActionInfo info = null;
		try
		{
			queueLock.lock();
			while(actions.isEmpty())
			{
				queueCondition.await();
			}
			
			while(!mediator.getCurrentAction().isFinished());
			action = actions.removeFirst();
		}catch(InterruptedException e)
		{
//			cicle = false;
		}finally
		{
			queueLock.unlock();
		}
		
		synchronized (mediator) 
		{
			System.out.println("AI IMPOSTA ACTION ");
			mediator.clearInformations();
			mediator.setAction(action.getActionGuid());
			while((info = action.pop())!= null)
			{
				System.out.println(info.getName());
				mediator.addActionInfo(info);
			}
			
		}
	}
	
	@Override
	public void preStartMatch(Map map) 
	{
		super.preStartMatch(map);
		runningReceiver.start();
	}

	@Override
	public void init() 
	{
		super.init();
		EventManager eventManager = EventManager.getInstance();
		eventManager.addListener(this, new MultipleAttacksEvent(0,0));
		eventManager.addListener(this, new MovementEvent(null, 0, 0, 0, 0));
		eventManager.addListener(this, new AttackEvent(false, false, null, null, null, 0.0f));
	}
	
	@Override
	public void notify(GenericEvent event) 
	{
		boolean swtichTurn = !turnUp;
		boolean matchOn = isMatchIsRunning();
		
		super.notify(event);
		
//		System.out.println("NOTIFY DI "+event.getName()+" e "+isMatchIsRunning());
		if(!matchOn && event.getGUID() == TurnEvent.GUID)
			{
			setMatchIsRunning(true);
			if(turnUp)
				receiveCondition.doNotify();
			}
		else if(matchOn)
		{
			if(event.getGUID() == TurnEvent.GUID && swtichTurn)
			{
				System.out.println(" - - - MANDO EVENTO svegliando running"+event.getName());
				receiveCondition.doNotify(); // Wake the running reciever
				connectionProtocol.sendEvent(event);
			}else 
			if(!turnUp && 
					(event.getGUID() == FinalMovementEventInformation.GUID || 
					event.getGUID() == AttackEvent.GLOBALLY_UNIQUE_IDENTIFIER || 
					event.getGUID() == MultipleAttacksEvent.GUID))
			{
					System.out.println(" - - -MANDO EVENTO "+event.getName());
					connectionProtocol.sendEvent(event);
			}
			/*else if(turnUp && event.getGUID() == TurnEvent.GUID)
			{
				System.out.println(" - - -MANDO EVENTO "+event.getName());
				connectionProtocol.sendEvent(event);
			}*/
//				}
//			}
		}
	}

	private class RunningReceiver extends Thread
	{

		/** */
		private OnLineEnemyController remote;
		
		/** */
		private boolean running; 
		private ServerCondition receiveCondition;
		private ServerCondition queueCondition;
		
		private RunningReceiver(OnLineEnemyController player, ServerCondition condition, ServerCondition queueCondition)
		{
//			run = true;
			this.receiveCondition = condition;
			this.queueCondition = queueCondition;
			remote = player;
		}
		
		public boolean isRunning() 
		{
			return running;
		}

		
		@Override
		public void run() 
		{ 
			ActionDeposit actionToAdd = null;
			boolean read = true;
			JSONObject receivedEvent = null;
			while(true) //TODO: inserire conditione di fine
			{
				try 
				{
					running = true;
					while(!remote.turnUp)
					{
						running = false;
						receiveCondition.doWait();
					}
//					System.out.println("running sveglio");
					running = true;
					read = true;
					
					try
					{
						// Time turn could end and we could be here to wait for the bytes at "readLine" of receiveEvent.
						// Just try to read for 150ms, if there're no bytes the socket throws SocketTimeoutException.
//						// Catch the exception, then check if remote player turn is still up; if it's go back to read.
						while(read)
						{
							receivedEvent = remote.getConnectionProtocol().receiveEvent(150);
							
							// We received the event, so we don't need to read anymore.
							read = false;
						}
						
						// This part will elaborate the received data. So it's here so it's going to be computed only if TimeoutException is not thrown by "receiveEvent()"
						actionToAdd = elaborate(receivedEvent);
						System.out.println("ricevuto evento "+actionToAdd.getActionGuid());
						
						remote.getQueueLock().lock();
						remote.getActions().add(actionToAdd);
						
						if(remote.getActions().size() > 0)
						{
							System.out.println("segnalo nuova azione");
							remote.getQueueCondition().signal();
						}
						remote.getQueueLock().unlock();
					}catch(SocketTimeoutException timeout)
					{
						// Do nothing. Just go to check if we must read again.
					}
				} catch (InterruptedException  | IOException e) 
				{
					e.printStackTrace();
//					remote.getQueueLock().unlock();
				}
			}
		}

		/** */
		private ActionDeposit elaborate(JSONObject receivedEvent) 
		{
			
			double guid = (Double) receivedEvent.get(GenericEvent.GLOBAL_GUID_KEY);
			ActionDeposit theReturn = new ActionDeposit();
			
			if(guid == AttackEvent.GLOBALLY_UNIQUE_IDENTIFIER)
			{
				System.out.println("RICEVUTO ATTACK EVENT");
				AttackEvent attack = new AttackEvent(false, false, null, null, null, 0.0f);
				attack.setFromJSONOnbject(receivedEvent);
				
				// If attack event has been created by a simply attack
				if(attack.getName().equalsIgnoreCase(CharacterAttackAction.NAME))
				{
					theReturn.setActionGuid(CharacterAttackAction.GUID);
					theReturn.push(new ActionCoordinateInfo(attack.getFrom().getRow(), attack.getFrom().getColumn()));
					theReturn.push(new ActionCoordinateInfo(attack.getTo().getRow(), attack.getTo().getColumn()));
				}
				// Event has been created by spell attack, and its name is the number of the selected ability
				else
				{
					theReturn.setActionGuid(AbilityAttackAction.GUID);
					theReturn.push(new ActionCoordinateInfo(attack.getFrom().getRow(), attack.getFrom().getColumn()));
					theReturn.push(new ActionSpellInfo(Integer.parseInt(attack.getName())));
					theReturn.push(new ActionCoordinateInfo(attack.getTo().getRow(), attack.getTo().getColumn()));
				}
			}
			// Multiple attacks event are thrown by ability actions
			else if(guid == MultipleAttacksEvent.GUID)
			{
				System.out.println("RICEVUTO MULTIPLE ATTACK EVENT");

				MultipleAttacksEvent attack = new MultipleAttacksEvent(0,0);
				attack.setFromJSONOnbject(receivedEvent);
				
				theReturn.setActionGuid(AbilityAttackAction.GUID);
				AttackEvent prototype = attack.getAttacks().getFirst();
				theReturn.push(new ActionCoordinateInfo(prototype.getFrom().getRow(), prototype.getFrom().getColumn()));
				theReturn.push(new ActionSpellInfo(Integer.parseInt(prototype.getName())));
				theReturn.push(new ActionCoordinateInfo(attack.getTargetRow(), attack.getTargetColumn()));
			}
			// All required informations of movement event are inside the FinalMovementEventInformation
			else if(guid == FinalMovementEventInformation.GUID)
			{
				System.out.println("RICEVUTO MOVEMENT EVENT");

				FinalMovementEventInformation movementEvent = new FinalMovementEventInformation(0, 0, 0, 0);
				movementEvent.setFromJSONOnbject(receivedEvent);
				
				theReturn.setActionGuid(MovementAction.GUID);
				theReturn.push(new ActionCoordinateInfo(movementEvent.getStartingRow(), movementEvent.getStartingColumn()));
				theReturn.push(new ActionCoordinateInfo(movementEvent.getEndingRow(), movementEvent.getEndingColumn()));
			}
			else if(guid == TurnEvent.GUID)
			{
				System.out.println("Ricevo END TURN ACTION");
				theReturn.setActionGuid(EndTurnAction.GUID);
			}
			else
			{
				theReturn.setActionGuid(ActionMediator.getNoAction().getActionIdentifier());
				System.out.println("RICEVUO SCONOSCIUTO ( "+guid+" )");
			}
					
			return theReturn;
		}
	}

	public BoomerangProtocol getConnectionProtocol() 
	{
		return connectionProtocol;
	}
	
	/**
	 * @return the queueLock
	 */
	public Lock getQueueLock() {
		return queueLock;
	}

	/**
	 * @return the queueCondition
	 */
	public Condition getQueueCondition() {
		return queueCondition;
	}

	/**
	 * @return the actions
	 */
	public LinkedList<ActionDeposit> getActions() {
		return actions;
	}

	@Override
	public void update() 
	{
//		System.out.println("sveglia runneing reciever");
//		condition.doNotify();
//		ActionDeposit action = null;
//		ActionMediator mediator = player.getActions();
//		GenericActionInfo info = null;
//		try
//		{
//			queueLock.lock();
//			while(actions.isEmpty())
//			{
//				queueCondition.await();
//			}
//			action = actions.removeFirst();
//		}catch(InterruptedException e)
//		{
////			cicle = false;
//		}finally
//		{
//			queueLock.unlock();
//		}
//		
//		try 
//		{
//			mediator.setAction(action.getActionGuid());
//			while((info = action.pop())!= null)
//			{
//				mediator.addActionInfo(info);
//			}
//
//			actionCondition.doWait();
//		} catch (InterruptedException e) 
//		{
//			e.printStackTrace();
//		}
	}

	@Override
	public void aiWait() 
	{
//		runningReceiver.sl;
	}

	public void setConnection(BoomerangProtocol connection) {
		this.connectionProtocol = connection;
	}

	/**
	 * @return the matchIsRunning
	 */
	public boolean isMatchIsRunning() {
		return matchIsRunning;
	}

	/**
	 * @param matchIsRunning the matchIsRunning to set
	 */
	public void setMatchIsRunning(boolean matchIsRunning) {
		this.matchIsRunning = matchIsRunning;
	}
}
